package com.jprocms.module.cms.controller.portal.api;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.CharsetUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.jpro.framework.common.enums.UserTypeEnum;
import com.jpro.framework.common.pojo.CommonResult;
import com.jpro.framework.common.pojo.PageResult;
import com.jpro.framework.common.util.string.StrUtils;
import com.jpro.framework.security.core.LoginUser;
import com.jpro.framework.security.core.util.SecurityFrameworkUtils;
import com.jpro.module.infra.api.config.FileConfigApi;
import com.jprocms.module.cms.config.JproProperties;
import com.jprocms.module.cms.controller.admin.content.relatevo.ContentRelateReqVO;
import com.jprocms.module.cms.controller.admin.content.vo.*;
import com.jprocms.module.cms.controller.directive.ContentListDirective;
import com.jprocms.module.cms.controller.portal.vo.ContentFrontRespVO;
import com.jprocms.module.cms.convert.ContentFrontConvert;
import com.jprocms.module.cms.convert.content.ContentConvert;
import com.jprocms.module.cms.convert.content.ContentCountConvert;
import com.jprocms.module.cms.dal.dataobject.content.ContentCountDO;
import com.jprocms.module.cms.dal.dataobject.content.ContentDO;
import com.jprocms.module.cms.enums.EnumContentStatus;
import com.jprocms.module.cms.enums.EnumFrontOperate;
import com.jprocms.module.cms.enums.EnumFrontOperateQueryType;
import com.jprocms.module.cms.es.service.ContentEsService;
import com.jprocms.module.cms.query.CustomFieldQuery;
import com.jprocms.module.cms.query.WhereCondition;
import com.jprocms.module.cms.service.channel.ChannelService;
import com.jprocms.module.cms.service.content.ContentCountService;
import com.jprocms.module.cms.service.content.ContentRelateService;
import com.jprocms.module.cms.service.content.ContentService;
import com.jprocms.module.cms.service.operate.OperateService;
import com.jprocms.module.cms.service.site.SiteService;
import com.jprocms.module.cms.util.CmsContextUtil;
import com.jprocms.module.cms.util.RequestUtils;
import com.jprocms.module.cms.util.SecureUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import static com.jpro.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.jpro.framework.common.pojo.CommonResult.error;
import static com.jpro.framework.common.pojo.CommonResult.success;
import static com.jprocms.module.cms.constant.CmsSysConstants.FRONT_API;
import static com.jprocms.module.cms.enums.ErrorCodeConstants.*;
import static com.jprocms.module.cms.util.Freemarkers.ORDER_TABLE_CONTENT_COUNT;
import static com.jprocms.module.cms.web.Directives.getOrderItems;

/**
 * @author jprocms
 */
@Slf4j
@Tag(name = "前台 - 内容对外交互接口")
@RestController
@RequestMapping(FRONT_API + "/content")
public class ContentApiController {

    @Resource
    private ContentEsService esContentService;
    @Autowired
    private ChannelService channelService;

    @Resource
    private SiteService siteService;
    @Resource
    private ContentService contentService;
    @Resource
    private JproProperties jproProperties;

    @Resource
    private ContentCountService contentCountService;

    @Resource
    private OperateService operateService;

    @Resource
    private ContentRelateService contentRelateService;

    @Resource
    private FileConfigApi fileConfigApi;


    @GetMapping(value = "/page")
    @Operation(summary = "内容分页接口")
    public CommonResult<PageResult<ContentFrontRespVO>> getContentPage(@ModelAttribute ContentFrontQueryReqVO queryDto) {
        queryDto = assembleQueryDto(queryDto);
        PageResult<ContentDO> contentPageFront = contentService.getContentPageFront(queryDto);
        return success(ContentFrontConvert.INSTANCE.convertPage(contentPageFront));
    }

    @GetMapping(value = "/list")
    @Operation(summary = "内容列表接口")
    public CommonResult<List<ContentFrontRespVO>> getContentList(@ModelAttribute ContentFrontQueryReqVO queryDto) {
        queryDto = assembleQueryDto(queryDto);
        List<ContentDO> contents = contentService.getContentListFront(queryDto);
        return success(ContentFrontConvert.INSTANCE.convertList(contents));
    }

    @PostMapping(value = "/page")
    @Operation(summary = "内容分页接口-POST请求")
    public CommonResult<PageResult<ContentFrontRespVO>> getContentPageByJson(@RequestBody  ContentFrontQueryReqVO queryDto) {
        queryDto = assembleQueryDto(queryDto);
        PageResult<ContentDO> contentPageFront = contentService.getContentPageFront(queryDto);
        return success(ContentFrontConvert.INSTANCE.convertPage(contentPageFront));
    }

    /**
     * 有自定义字段过滤需求用post请求 customMap get不好传递
     * @param queryDto
     * @return
     */
    @PostMapping(value = "/list")
    @Operation(summary = "内容列表接口-POST请求")
    public CommonResult<List<ContentFrontRespVO>> getContentListByJson(@RequestBody   ContentFrontQueryReqVO queryDto) {
        queryDto = assembleQueryDto(queryDto);
        List<ContentDO> contents = contentService.getContentListFront(queryDto);
        return success(ContentFrontConvert.INSTANCE.convertList(contents));
    }

    private ContentFrontQueryReqVO assembleQueryDto(ContentFrontQueryReqVO queryDto) {
        Long currSiteId = CmsContextUtil.getCurrSiteId();
        // 不获取所有站点，则给默认站点ID
        if (queryDto.getSiteId() == null && !queryDto.getAllSite()) {
            queryDto.setSiteId(currSiteId);
        }
        Collection<Long> channelIds = ContentListDirective.getChannelIds(queryDto.getChannelIds(), queryDto.getChannelPaths(), queryDto.getSiteId(), queryDto.getIncludeChildChannel(), channelService);
        queryDto.setChannelIds(channelIds);
        queryDto.setStatus(EnumContentStatus.PUBLISHED.getStatus());
        Integer maxPage = queryDto.getPageMax();
        Integer pageNo = queryDto.getPageNo();
        if (maxPage != null && pageNo > maxPage) {
            pageNo = maxPage;
        }
        queryDto.setPageNo(pageNo);

        if (queryDto.getOrderBy() != null) {
            Collection<OrderItem> orderItems = new ArrayList<>();
            if (queryDto.getOrderBy() == 1) {
                orderItems = getOrderItems(StrUtils.splitToString("release_time_desc,id_desc"));
            } else if (queryDto.getOrderBy() == 2) {
                orderItems = getOrderItems(StrUtils.splitToString("release_time_asc,id_asc"));
            } else if (queryDto.getOrderBy() == 3) {
                orderItems = getOrderItems(StrUtils.splitToString("create_time_desc,id_desc"));
            } else if (queryDto.getOrderBy() == 4) {
                orderItems = getOrderItems(StrUtils.splitToString("create_time_asc,id_asc"));
            } else if (queryDto.getOrderBy() == 5) {
                orderItems = getOrderItems(StrUtils.splitToString("count.views_desc,id_desc"));
            } else if (queryDto.getOrderBy() == 7) {
                orderItems = getOrderItems(StrUtils.splitToString("count.views_asc,id_asc"));
            } else if (queryDto.getOrderBy() == 8) {
                orderItems = getOrderItems(StrUtils.splitToString("count.comments_desc,id_desc"));
            } else if (queryDto.getOrderBy() == 9) {
                orderItems = getOrderItems(StrUtils.splitToString("count.comments_asc,id_asc"));
            } else if (queryDto.getOrderBy() == 10) {
                orderItems = getOrderItems(StrUtils.splitToString("count.ups_desc,id_desc"));
            } else if (queryDto.getOrderBy() == 11) {
                orderItems = getOrderItems(StrUtils.splitToString("count.ups_asc,id_asc"));
            } else if (queryDto.getOrderBy() == 12) {
                orderItems = getOrderItems(StrUtils.splitToString("count.downs_desc,id_desc"));
            } else if (queryDto.getOrderBy() == 13) {
                orderItems = getOrderItems(StrUtils.splitToString("count.downs_asc,id_asc"));
            } else if (queryDto.getOrderBy() == 13) {
                orderItems = getOrderItems(StrUtils.splitToString("count.downloads_desc,id_desc"));
            } else if (queryDto.getOrderBy() == 14) {
                orderItems = getOrderItems(StrUtils.splitToString("count.downloads_asc,id_asc"));
            }
            queryDto.setOrderItems(orderItems);
        }
        //默认置顶优先，发布时间降序排序
        if (CollUtil.isEmpty(queryDto.getOrderItems())) {
            Collection<OrderItem> orderItems = getOrderItems(StrUtils.splitToString("top_level_desc,release_time_desc"));
            queryDto.setOrderItems(orderItems);
        }
        //目前就考虑排序拼接额外连接的表
        queryDto.setJoinTables(getJoinTables(queryDto.getOrderItems()));
        queryDto.setQuerySubChannel(jproProperties.isPublishMultiChannel());
        List<WhereCondition> customsConditions = CustomFieldQuery.parse(queryDto.getCustomMap());
        queryDto.setCustomsConditions(customsConditions);
        return queryDto;
    }

    public static Collection<JoinTable> getJoinTables(Collection<OrderItem> orderItems) {
        Set<JoinTable> joinTables = new HashSet<>();
        if (CollUtil.isNotEmpty(orderItems)) {
            final List<OrderItem> orderItemList = orderItems.stream().filter(c -> StringUtils.isNotBlank(c.getColumn())).collect(Collectors.toList());
            for (OrderItem orderItem : orderItemList) {
                String column = orderItem.getColumn();
                if (column.contains(ORDER_TABLE_CONTENT_COUNT)) {
                    joinTables.add(new JoinTable("cms_content_count", "count", "id", "id"));
                }
            }
        }
        return joinTables;
    }


    @GetMapping(value = "/relation/list")
    @Operation(summary = "相关内容列表")
    public CommonResult<List<ContentFrontRespVO>> getRelateList(@ModelAttribute ContentRelateReqVO queryDto) {
        return success(ContentFrontConvert.INSTANCE.convertList(contentRelateService.getContentRelateListFront(queryDto)));
    }


    @Operation(summary = "获取内容详情")
    @ApiResponses(value = {@ApiResponse(description = "内容对象")})
    @GetMapping("/{id:[\\d]+}")
    public CommonResult<ContentRespVO> show(@Parameter(description = "内容ID") @PathVariable Long id,
                                            @Parameter(description = "是否预览") @RequestParam(defaultValue = "false") boolean preview, HttpServletRequest request) {
        ContentDO contentDO = contentService.getContent(id);
        if (contentDO == null) {
            log.error("content not found. ID: " + id);
            return error(CONTENT_NOT_EXISTS);
        }
        //不是发布状态不能取(管理员登录，可以获取)
        if(!EnumContentStatus.PUBLISHED.getStatus().equals(contentDO.getStatus())){
            LoginUser user = SecurityFrameworkUtils.getLoginUser();
            if(user!=null&&UserTypeEnum.ADMIN.getValue().equals(user.getUserType())){
                return success(ContentConvert.INSTANCE.convertMore(contentDO));
            }
            return error(CONTENT_NOT_EXISTS);
        }
        return success(ContentConvert.INSTANCE.convertMore(contentDO));
    }

    public static void checkVisitPermission(ContentDO contentDO, LoginUser user, boolean preview) {
        if (preview) {
            if (user == null) {
                error(NO_AUTHORITY);
            }
            //非管理员没有权限
            if (!UserTypeEnum.ADMIN.getValue().equals(user.getUserType())) {
                error(NO_AUTHORITY);
            }
            return;
        }
        //非预览 未发布没有权限访问
        if (!contentDO.isPublish()) {
            error(NO_AUTHORITY);
        }
    }

    @Operation(summary = "获取上下篇内容")
    @ApiResponses(value = {@ApiResponse(description = "获取上下篇内容")})
    @GetMapping("/getContentSide")
    public CommonResult<ContentRespVO> getContentSide(@Valid ContentSideReqVO contentSideReqVo) {
        Long id = contentSideReqVo.getId();
        ContentDO content = contentService.getContent(id);
        if (content == null) {
            return success(null);
        }
        contentSideReqVo.setReleaseTime(content.getReleaseTime());
        contentSideReqVo.setTopLevel(content.getTopLevel());
        if (contentSideReqVo.getSiteId() == null) {
            contentSideReqVo.setSiteId(CmsContextUtil.getCurrSiteId());
        }
        ContentDO contentDO = contentService.findSide(contentSideReqVo, contentSideReqVo.getNext());
        if (contentDO == null) {
            return success(null);
        }
        return success(ContentConvert.INSTANCE.convertMore(contentDO));
    }

    @Operation(summary = "获取内容统计数据")
    @GetMapping("/count/{id:[\\d]+}")
    public CommonResult<ContentCountRespVO> getCount(@Parameter(description = "内容ID") @PathVariable Long id) {
        ContentCountDO countDO = contentCountService.getById(id);
        if (countDO == null) {
            log.error("Content not found: contentId=", id);
            throw exception(CONTENT_NOT_EXISTS);
        }
        return success(ContentCountConvert.INSTANCE.convert(countDO));
    }

    @Operation(summary = "获取内容浏览次数")
    @GetMapping("/view/{id:[\\d]+}")
    public CommonResult<Integer> view(@Parameter(description = "内容ID") @PathVariable Long id) {
        return success(contentCountService.view(id));
    }

    @Operation(summary = "顶内容")
    @ApiResponses(value = {@ApiResponse(description = "内容总顶次数")})
    @PostMapping("/up/{id:[\\d]+}")
    public CommonResult<Integer> up(@Parameter(description = "内容ID") @PathVariable Long id, HttpServletRequest request, HttpServletResponse response) {
        ContentCountDO countDO = contentCountService.getById(id);
        if (countDO == null) {
            return success(0);
        }
        if (operateService.existOperate(id, null, EnumFrontOperate.UP_DOWN, EnumFrontOperateQueryType.ALL, request, response)) {
            return success(-1);
        }
        int ups = countDO.getUps() + 1;
        countDO.setUps(ups);
        contentCountService.updateById(countDO);
        return success(ups);
    }

    @Operation(summary = "踩内容")
    @ApiResponses(value = {@ApiResponse(description = "内容总踩次数")})
    @PostMapping("/down/{id:[\\d]+}")
    public CommonResult<Integer> down(@Parameter(description = "内容ID") @PathVariable Long id, HttpServletRequest request, HttpServletResponse response) {
        ContentCountDO countDO = contentCountService.getById(id);
        if (countDO == null) {
            return success(0);
        }
        if (operateService.existOperate(id, null, EnumFrontOperate.UP_DOWN, EnumFrontOperateQueryType.ALL, request, response)) {
            return success(-1);
        }
        int downs = countDO.getDowns() + 1;
        countDO.setDowns(downs);
        contentCountService.updateById(countDO);
        long cookie = RequestUtils.resolveIdentityCookie(request, response);
        String ip = RequestUtils.getRemoteAddr(request);
        Long userId = Optional.ofNullable(SecurityFrameworkUtils.getLoginUserId()).orElse(null);
        operateService.insertOperate(id, EnumFrontOperate.UP_DOWN, cookie, ip, userId);
        return success(downs);
    }

    @Operation(summary = "获取下载参数")
    @ApiResponses(value = {@ApiResponse(description = "当前时间和KEY。如：`time=123&key=abc`")})
    @GetMapping("/download-getkey/{id:[\\d]+}")
    public CommonResult<String> downloadParam(@Parameter(description = "内容ID") @PathVariable Long id) {
        long time = System.currentTimeMillis();
        String secret = jproProperties.getDownloadSecret();
        String key = SecureUtil.getDownloadKey(id, time, secret);
        return success("time=" + time + "&key=" + key);
    }

    @Operation(summary = "记录下载次数")
    @ApiResponses(value = {@ApiResponse(description = "内容总下载次数")})
    @PostMapping("/download/{id:[\\d]+}")
    public CommonResult<Integer> downloadCount(@Parameter(description = "内容ID") @PathVariable Long id) {
        ContentCountDO countDO = contentCountService.getById(id);
        if (countDO == null) {
            return success(0);
        }
        int downloads = countDO.getDownloads() + 1;
        countDO.setDownloads(downloads);
        contentCountService.updateById(countDO);
        return success(downloads);
    }

    /**
     * 下载附件
     *
     * @param id
     * @param index
     * @param sitePath
     * @param time
     * @param key
     * @param preview
     * @param request
     * @param response
     * @throws IOException
     */
    @GetMapping({"/download/file/get/{id:[\\d]+}", "/download/file/get/{id:[\\d]+}/{index:[\\d]+}",
            "/{sitePath:[\\w-]+}/download/file/get/{id:[\\d]+}", "/{sitePath:[\\w-]+}/download/file/get/{id:[\\d]+}/{index:[\\d]+}"})
    public void download(@PathVariable Long id, @PathVariable(required = false) Integer index,
                         @PathVariable(required = false) String sitePath,
                         @RequestParam long time, @NotNull String key,
                         @RequestParam(defaultValue = "false") boolean preview,
                         HttpServletRequest request, HttpServletResponse response) throws IOException {
        if (!SecureUtil.validateDownloadKey(key, id, time, jproProperties.getDownloadSecret())) {
            error(DOWN_KEY_VALID);
        }
        int expires = 24 * 60 * 60 * 1000;
        if (System.currentTimeMillis() - time > expires) {
            error(DOWN_KEY_VALID);
        }
        LoginUser user = SecurityFrameworkUtils.getLoginUser();
        ContentDO content = contentService.getContent(id);
        if (content == null) {
            error(CONTENT_NOT_EXISTS);
        }
        checkVisitPermission(content, user, preview);
        String fileUrl;
        String fileName;
        if (index != null) {
            List<ContentFileVo> fileList = content.getAttachs();
            if (index >= fileList.size()) {
                log.error("Content file not found. id=" + id + ", index=" + index);
                error(DOWN_FILE_NOTEXIST_VALID);
            }
            ContentFileVo contentFileVo = fileList.get(index);
            fileUrl = contentFileVo.getUrl();
            fileName = contentFileVo.getName();
        } else {
            fileUrl = content.getSysAttach();
            fileName = content.getSysAttachName();
        }
        if (StringUtils.isBlank(fileUrl)) {
            log.error("Content file not found. id=" + id);
            error(DOWN_FILE_NOTEXIST_VALID);
        }

        //更新下载数量
        ContentCountDO countDO = contentCountService.getById(id);
        int downloads = countDO.getDownloads() + 1;
        countDO.setDownloads(downloads);
        contentCountService.updateById(countDO);
        //直接跳转到真实下载地址
        fileUrl = fileUrl + "?name=" + Base64.encode(fileName, CharsetUtil.CHARSET_UTF_8.name());
        response.sendRedirect(fileUrl);
    }


}
